#include <iostream>          // For cout and cerr
#include <cstdlib>           // For atoi()
#include <cstring>
#include <fstream>
#include <netinet/in.h>
#include <vector>
#include "Blowfish.h"
#include "Packet.h"
#include "PracticalSocket.h" // For UDPSocket and SocketException

using namespace std;

const int packetSize = 1024 + PADDING_SIZE;     // Longest string to echo

bool encryption = true;
int senderWinSize = 4; // 1 = sw
int recvWinSize = 1; // 1 = gbn or sw, >1 sr. Always < senderWinSize
int maxSeqNum = 2 * senderWinSize;
short expectedSeqNum = 0;
short currPackNum = 0;
int timeoutLength = 0; // nanoseconds
vector<Packet*> window(recvWinSize, NULL);

char buffer[packetSize];
ifstream myFile;
Blowfish bf;

string sourceAddress;
unsigned short sourcePort;
bool eofFound = false;
bool finished = false;

int main(int argc, char *argv[]) {
	if (argc != 2) {                  // Test for correct number of parameters
		cerr << "Usage: " << argv[0] << " <Server Port>" << endl;
		exit(1);
	}

	unsigned short serverPort = atoi(argv[1]);     // First arg:  local port

	bf.Set_Passwd("123456789pass");

	try {
		UDPSocket sock(serverPort);                

		int recvMsgSize;                  // Size of received message

		ofstream myFile;
		// @TODO File needs to be entered in from prompt
		myFile.open("bryantcj_test", fstream::out | fstream::binary | fstream::trunc);

		while (!finished) {  // Run forever
			memset(buffer, 0, packetSize);
			// Block until receive message from a client
			recvMsgSize = sock.recvFrom(buffer, packetSize, sourceAddress, sourcePort); 

			Packet* pb = new Packet(buffer, packetSize, &bf);
			short seqNum = pb->seqNum;
			short packNum = pb->packetNum;
			cout << "Received packet from " << sourceAddress << ":" << sourcePort << endl;
			cout << "Seq: " << seqNum << " recieved" << endl;

			if (pb->checkCrc()) {
				cout << "Start: " << expectedSeqNum << " End: " << (expectedSeqNum + recvWinSize - 1) % maxSeqNum << endl;
				if (seqNum >= expectedSeqNum && seqNum <= expectedSeqNum + recvWinSize - 1) { // In the window
					cout << "In the window" << endl;
					int index = seqNum % recvWinSize;
					if (window.at(index) != NULL) {
						// Duplicate packet, only send ack
						// Don't think this can happen but just in case
						cout << "Duplicate packet " << seqNum << endl;
					} else {
						// Buffer the packet
						window.at(index) = pb;

						// Only if you are the first in the window
						if (seqNum % recvWinSize == expectedSeqNum % recvWinSize) {
							index = expectedSeqNum % recvWinSize;
							while (window.at(index) != NULL) {
								// Move Window
								cout << "Next window: " << (expectedSeqNum + 1) % maxSeqNum << " - " << (((expectedSeqNum + 1) % maxSeqNum) + recvWinSize - 1) % maxSeqNum << endl;
								expectedSeqNum = (expectedSeqNum + 1) % maxSeqNum;
								currPackNum++;

								// Write to file
								if (encryption) window.at(index)->decryptPayload(sourceAddress);
								if (myFile.is_open()) {
									myFile.write(window.at(index)->getPayload(), window.at(index)->getPayloadSize());
									myFile.flush();
								}
								
								// Reset packet
								delete window.at(index);
								window.at(index) = NULL;

								index = expectedSeqNum % recvWinSize;
							}
						}
					}

					// Send ack
					sock.sendTo(pb->buildAck(), packetSize, sourceAddress, sourcePort);
                                        cout << "Ack " << seqNum << " sent" << endl;
				} else if (packNum < currPackNum) { // Behind you, send ack only
					cout << "Behind the window" << endl;
					sock.sendTo(pb->buildAck(), packetSize, sourceAddress, sourcePort);
					cout << "Ack " << seqNum << " sent" << endl;
				} // Ignore ahead of you
			} else {
				cout << "Corrupt packet!" << endl;
			}

			// @TODO All packets need to be cleared thru before ending!
			if (pb->isEof()) {
				finished = true;
			}
/*
			if (eofFound) {
				finished = true;
				for (unsigned int i = 0; i < window.size(); i++) {
					if (window.at(i) != NULL) {
						finished = false;
					}
				}
			}
*/
		}

		myFile.close();
	} catch (SocketException &e) {
		cerr << e.what() << endl;
		exit(1);
	}
	// NOT REACHED

	return 0;
}

